﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;

namespace RadiantQ.Windows.Controls
{
    /// <summary>
    /// Lets you visualize a hierarchical data source in a multi-column tree layout. The ItemsSource for this control
    /// should be bound to a FlatteningTreeView instance's FlatItemsSource property.
    /// </summary>
    /// <remarks>
    /// When bound to a FlatteningTreeView.FlatItemsSource property, the 
    /// DataContext for each DataGridRow will be an instance of <see
    /// cref="T:RadiantQ.Windows.Controls.FlatNodeData">FlatNodeData</see>.
    /// The SelectedItem property will also be an instance of the FlatNodeData.
    /// FlatNodeData.Data can be cast to your bound items in the hieararchy.
    /// </remarks>
    public class MultiColumnTreeList : DataGrid
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public MultiColumnTreeList()
        {
            this.AutoGenerateColumns = false;
            this.HeadersVisibility = DataGridHeadersVisibility.All;

            this.CanUserSortColumns = false;
        }


        private double RowHeightInternal
        {
            get { return (double)GetValue(RowHeightInternalProperty); }
            set { SetValue(RowHeightInternalProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RowHeightInternal.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RowHeightInternalProperty =
            DependencyProperty.Register("RowHeightInternal", typeof(double), typeof(MultiColumnTreeList), new PropertyMetadata(0.0));


        /// <summary>
        /// Called while loading a row.
        /// </summary>
        protected override void OnLoadingRow(DataGridRowEventArgs e)
        {
            DataGridRow row = e.Row;

            // DataContext will be FlatNodeData

            Binding bind = new Binding("IsVisibleInTree");
            bind.Mode = BindingMode.OneWay;
            bind.Converter = new BoolToVisibilityConverter();
            row.SetBinding(VisibilityProperty, bind);

            bind = new Binding("IsParentType");
            bind.Converter = new BoolToBoldConverter();
            row.SetBinding(FontWeightProperty, bind);

            base.OnLoadingRow(e);

        }
        /// <summary>
        /// Override.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.OnTempalteApplied();
        }

        /// <summary>
        /// Occurs once during the lifetime of the control, after it's templates are
        /// applied.
        /// </summary>
        public event EventHandler TemplateApplied;

        /// <summary>
        /// Call this to fire the TempalteApplied event.
        /// </summary>
        protected virtual void OnTempalteApplied()
        {
            if (this.TemplateApplied != null)
                this.TemplateApplied(this, EventArgs.Empty);
        }

        /// <summary>
        /// Override.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
        {
            if (e.EditingElement is IRefreshable)
            {
                ((IRefreshable)e.EditingElement).RefreshValue();
            }
            base.OnCellEditEnding(e);
        }
    }
    /// <summary>
    /// Implement this interface in any custom controls you might use in the GanttTable column's editing templates.
    /// This will be called by the GanttTable right before the cell will exit edit mode.
    /// </summary>
    /// <remarks>
    /// If you notice that the user edited values are being ignored in the GanttTable's editing mode, implement this interface
    /// and in the RefreshValue method, make sure that the control's Value is up to date.
    /// </remarks>
    public interface IRefreshable
    {
        /// <summary>
        /// This method will be called to refresh the value.
        /// </summary>
        void RefreshValue();
    }
    /// <summary>
    /// Converts bool to Visibility.
    /// </summary>
    ///<compilewhen>never</compilewhen>
    public class BoolToVisibilityConverter : IValueConverter
    {
        /// <summary>
        /// If true, false will be converted to Visible and true to Hidden.
        /// </summary>
        protected bool Reverse { get; set; }
        #region IValueConverter Members

        /// <summary>
        /// Converts.
        /// </summary>
        public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool visible = (bool)value;
            if (Reverse == false)
                return visible ? Visibility.Visible : Visibility.Collapsed;
            else
                return visible ? Visibility.Collapsed : Visibility.Visible;
        }
        /// <summary>
        /// Converts back.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Visibility visible = (Visibility)value;
            if (Reverse == false)
                return visible == Visibility.Visible ? true : false;
            else
                return visible == Visibility.Visible ? false : true;
        }

        #endregion
    }

    /// <summary>
    /// Converts bool to FontWeights.Bold
    /// </summary>
    ///<compilewhen>never</compilewhen>
    public class BoolToBoldConverter : IValueConverter
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public BoolToBoldConverter() { }

        #region IValueConverter Members

        /// <summary>
        /// Converts.
        /// </summary>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool reverse = parameter == null ? false : true;
            bool b = (bool)value;
            if (reverse == false)
                return b ? FontWeights.Bold : FontWeights.Normal;
            else
                return b ? FontWeights.Normal : FontWeights.Bold;
        }
        /// <summary>
        /// Converts back.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /// <summary>
    /// A StackPanel container that contains an expand/collapse arrow. Also includes a Text property which can be bound to 
    /// a TextBlock, TextBox, etc. in a derived class.
    /// </summary>
    public abstract class ExpandableTextContainer : StackPanel
    {
        private Border marginBorder;
        /// <summary>
        /// Constructor.
        /// </summary>
        public ExpandableTextContainer()
        {
            this.Margin = new Thickness(0, 2, 0, 2);
            this.Orientation = Orientation.Horizontal;
            this.VerticalAlignment = VerticalAlignment.Stretch;

            Binding binding = new Binding("IsParentType");
            this.SetBinding(IsParentTypeProperty, binding);

            binding = new Binding("Level");
            this.SetBinding(LevelProperty, binding);

            Border border = new Border() { BorderThickness = new Thickness(0), Background = new SolidColorBrush(Colors.Transparent) };
            this.marginBorder = border;
            this.Children.Add(border);

            ExpanderCue expanderCue = new ExpanderCue();
            expanderCue.Margin = new Thickness(0, 0, 0, 0);

            binding = new Binding("TreeViewItem.IsExpanded");
            binding.Mode = BindingMode.TwoWay;
            expanderCue.SetBinding(ExpanderCue.IsExpandedProperty, binding);

            binding = new Binding("IsExpandable");
            binding.Converter = new BoolToVisibilityConverter();
            expanderCue.SetBinding(VisibilityProperty, binding);

            this.Children.Add(expanderCue);

        }
        /// <summary>
        /// Specifies if the bound data is a parent type.
        /// </summary>
        public bool IsParentType
        {
            get { return (bool)GetValue(IsParentTypeProperty); }
            private set { SetValue(IsParentTypeProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.ExpandableTextContainer.IsParentType">IsParentType</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty IsParentTypeProperty =
            DependencyProperty.Register("IsParentType", typeof(bool), typeof(ExpandableTextContainer), new PropertyMetadata(false, WidthSourceChanged));

        /// <summary>
        /// Gets the level of the row in the hierarchy.
        /// </summary>
        public int Level
        {
            get { return (int)GetValue(LevelProperty); }
            private set { SetValue(LevelProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.ExpandableTextContainer.Level">Level</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty LevelProperty =
            DependencyProperty.Register("Level", typeof(int), typeof(ExpandableTextContainer), new PropertyMetadata(-1, WidthSourceChanged));

        private static void WidthSourceChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            ((ExpandableTextContainer)source).ApplyWidth();
        }

        private void ApplyWidth()
        {
            FlatNodeData fnd = this.DataContext as FlatNodeData;
            this.marginBorder.Width = FlatteningTreeView.LevelToIndentWidth(fnd.Level, fnd.IsParentType);
        }
        
        /// <summary>
        /// Specifes the string value retrieved from the bound object.
        /// </summary>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.Text">Text</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(ExpandableTextContainer), new PropertyMetadata(String.Empty));


    }
    /// <summary>
    /// A StackPanel type with an arrow for expand/collapse and a TextBlock to display
    /// the value of the <see
    /// cref="P:RadiantQ.Windows.Controls.ExpandableTextContainer.Text">Text</see>
    /// property. Use this type in the DataGridTemplateColumn.CellTemplate for the first column in a MultiColumnTreeList
    /// to implement expand/collapse functionality.
    /// </summary>
    public class ExpandableTextBlock : ExpandableTextContainer
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public ExpandableTextBlock()
        {
            this.VerticalAlignment = VerticalAlignment.Center;

            TextBlock tb = new TextBlock() { Margin = new Thickness(3, 0, 4, 0) };
            Binding binding = new Binding("Text");
            binding.Source = this;
            tb.SetBinding(TextBlock.TextProperty, binding);

            this.Children.Add(tb);
        }
    }
    /// <summary>
    /// A StackPanel type with an arrow for expand/collapse and a TextBox to display
    /// the value of the <see
    /// cref="P:RadiantQ.Windows.Controls.ExpandableTextContainer.Text">Text</see>
    /// property. Use this type in the DataGridTemplateColumn.CellEditingTemplate for the first column in a MultiColumnTreeList
    /// to implement expand/collapse functionality.
    /// </summary>
    public class ExpandableTextBox : ExpandableTextContainer, IRefreshable
    {
        TextBox TextBox { get; set; }
        /// <summary>
        /// Constructor
        /// </summary>
        public ExpandableTextBox()
        {
            TextBox tb = new TextBox() { Margin = new Thickness(3, 0, 4, 0) };
            Binding binding = new Binding("Text");
            binding.Source = this;
            tb.SetBinding(TextBox.TextProperty, binding);

            this.Children.Add(tb);

            this.TextBox = tb;
        }

        #region IRefreshable Members
        /// <summary>
        /// Refreshes the Text based on the current value in the textbox.
        /// </summary>
        public void RefreshValue()
        {
            this.Text = this.TextBox.Text;
        }

        #endregion
    }
}
